/*  Beamed (the only limit is your imagination) */
/*  Copyright (C) 2012  Michael Fürst */
/*  */
/*  This program is free software; you can redistribute it and/or modify it */
/*  under the terms of the GNU General Public License as published by the */
/*  Free Software Foundation; either version 3 of the License, or (at your */
/*  option) any later version. */
/*  */
/*  This program is distributed in the hope that it will be useful, but */
/*  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY */
/*  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License */
/*  for more details. */
/*  */
/*  You should have received a copy of the GNU General Public License along */
/*  with this program; if not, see <http://www.gnu.org/licenses/>. */

function HCP_CObjectManager() {
	/* misc */
	var that = this;
	var RenderEngine = null;
	var running = false;
	/* fps mesurement */
	var fps = 60;
	var fps_timestamp = new Date().getTime();
	/* moving along the path */
	var frame = 0.0; // this var actually keeps track of the current percent inbetween two pathpositions, it is no absolute frame, because there are no absolute framepositions
	var next = new CDefaultPath();
	var last = new CDefaultPath();
	var step = 0;
	var mode = 0;
	var Path = new CList();
	var Objects = new CList();
	/* mousemovement */
	var mouse = {
		isDown : false,
		pos : null
	};
	var last_cur_pos = {
		x : 0,
		y : 0
	};
	var dragged = false;
	var presstime = 0;
	/* display settings */
	var scalar = 1;
	var angle = 0;
	var dx = 0;
	var dy = 0;
	var direction = 1; // Modifier to give the possibility for a smooth and animated go back to previous pathpos

	/* smoothly go to the next path position */
	/* the name next was already in use */
	function next_one() {
		if (direction > 0) {
			step += 1;
		}
		direction = 1;
		mode = 0;
		frame = 0.0;
		if (step >= Path.getLength()) {
			step = 0;
		}
		if (step - 1 < 0) {
			last = Path.getObject(Path.getLength() - 1);
		} else {
			last = Path.getObject(step - 1);
		}
		if (last == null) {
			last = new CDefaultPath();
		}
		next = Path.getObject(step);
		if (next == null) {
			next = new CDefaultPath();
		}
		RenderEngine.clearBuffer();
		Objects.foreach(function(obj) {
			if (obj.bufferable == true) {
				RenderEngine.appendBuffer(obj);
			}
		});
	}
	/* smoothly go to the previous path position */
	function prev_one() {
		if (direction < 0) {
			step -= 1;
		}
		mode = 0;
		direction = -1;
		if (step < 0) {
			step = Path.getLength() - 1;
			if (step < 0)
				step = 0;
		}
		frame = 99.9;
		next = Path.getObject(step);
		if (next == null) {
			next = new CDefaultPath();
		}
		if (step - 1 < 0) {
			last = Path.getObject(Path.getLength() - 1);
		} else {
			last = Path.getObject(step - 1);
		}
		if (last == null) {
			last = new CDefaultPath();
		}
		RenderEngine.clearBuffer();
		Objects.foreach(function(obj) {
			if (obj.bufferable == true) {
				RenderEngine.appendBuffer(obj);
			}
		});
	}
	
	/* registers mousedown event */
	function onmousedown(e) {
		if (e.which == BUTTON_LEFT) {
			dragged = false;
			var pos = getCursorPosition(e);
			mouse.isDown = true;
			mouse.pos = pos;
			presstime = new Date().getTime();
		}
		if (e.which == BUTTON_MIDDLE) {
			dragged = false;
			var pos = getCursorPosition(e);
			mouse.mid = true;
			mouse.pos = pos;
		}
	}
	/* if mouse is not dragged call mouse-click-listener */
	function onmouseup(e) {
		if (e.which == BUTTON_LEFT) {
			mode = 1;
			mouse.isDown = false;
			if (dragged != true
					&& typeof (Presentation.mouse_click_listener) == 'function') {
				presstime = (new Date().getTime()) - presstime;
				var dx2 = Math.cos(-angle * Math.PI / 180)
						* ((mouse.pos.x - RenderEngine.getSize().width / 2) / scalar)
						- Math.sin(-angle * Math.PI / 180)
						* ((mouse.pos.y - height / 2) / scalar);
				var dy2 = Math.sin(-angle * Math.PI / 180)
						* ((mouse.pos.x - RenderEngine.getSize().width / 2) / scalar)
						+ Math.cos(-angle * Math.PI / 180)
						* ((mouse.pos.y - height / 2) / scalar);
				Presentation.mouse_click_listener(-dx / scalar + dx2, -dy
						/ scalar + dy2, angle, scalar, partial, presstime);
			} else if (dragged == false) {
				next_one();
			}
			presstime = 0;
		}
		if (e.which == BUTTON_MIDDLE) {
			mode = 1;
			mouse.mid = false;
			if (dragged == false && Presentation.mouse_click_listener != null) {

			}
		}
	}
	/* Here the free movement is implemented (on mouse drag) */
	function onmousemove(e) {
		var pos = getCursorPosition(e);
		mouse.pos = pos;
		if (mouse.isDown == true) {
			dragged = true;
			mode = 1;
			dx += Math.cos(angle * Math.PI / 180) * (pos.x - last_cur_pos.x)
					+ Math.sin(angle * Math.PI / 180)
					* (pos.y - last_cur_pos.y);
			dy += -Math.sin(angle * Math.PI / 180) * (pos.x - last_cur_pos.x)
					+ Math.cos(angle * Math.PI / 180)
					* (pos.y - last_cur_pos.y);
			presstime = new Date().getTime();
		}
		if (mouse.mid == true) {
			dragged = true;
			mode = 1;
			angle += (pos.x - last_cur_pos.x) / RenderEngine.getSize().width * 360;
		}
		last_cur_pos = pos;
	}
	/* keyboard input handler */
	function onkeydown(e) {
		if (Presentation.keyboard_input_state == true) {
			var key = e.keyCode;
			switch (key) {
			case KEY_F:
				toggle_fullscreen();
				break;
			case KEY_F11:
				force_fullscreen(true);
				break;
			case KEY_ESCAPE:
				mode = 0;
				step = 0;
				frame = 0;
				force_fullscreen(false);
				break;
			case KEY_RIGHT:
				next_one();
				break;
			case KEY_PAGE_DOWN:
				next_one();
				break;
			case KEY_SPACE:
				next_one();
				break;
			case KEY_LEFT:
				prev_one();
				break;
			case KEY_PAGE_UP:
				prev_one();
				break;
			case KEY_NEG_ROTATION:
				mode = 1;
				angle--;
				break;
			case KEY_POS_ROTATION:
				mode = 1;
				angle++;
				break;
			/* browser reserved */
			case KEY_PLUS:
				break; // only usable in chrome :/
			case KEY_MINUS:
				break; // only usable in chrome :/
			case KEY_F5:
				break;
			case KEY_BACKSPACE:
				break;
			/* pass all that was not caught to custom handler */
			default:
				if (Presentation.external_key_handler != null)
					Presentation.external_key_handler(key, angle, scalar,
							RenderEngine.getPartial());
				break;
			}
		}
	}
	function onwheel(event) {
		if (Presentation.mousewheel == false)
			return;
		var delta = 0;
		if (!event)
			event = window.event;
		if (event.wheelDelta) {
			delta = event.wheelDelta / 120;
		} else if (event.detail) {
			delta = -event.detail / 3;
		}
		var final_delta = Math.round(delta); // Safari Round
		if (final_delta > 0) {
			plus();
		} else if (final_delta < 0) {
			minus();
		}
	}

	/* switch fullscreen flag & change pos attr for correct positioning */
	function toggle_fullscreen() {
		RenderEngine.toggleFullscreen();
	}
	/* force a certain fullscreen state */
	function force_fullscreen(b_full) {
		RenderEngine.forceFullscreen();
	}
	
	/* update cursor position (required for free mode) */
	function getCursorPosition(e) {
		var pos = {
			x: 0, y: 0}
		;
		if (e.pageX != undefined && e.pageY != undefined) {
			pos.x = e.pageX;
			pos.y = e.pageY;
		}
		else {
			pos.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
			pos.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
		}
		var canv_pos = RenderEngine.getCanvasPosition();
		pos.x -= canv_pos.x;
		pos.y -= canv_pos.y;
		return pos;
	}
	
	/* zoom in */
	function plus () {
		if (scalar < 10000) {
			mode = 1;
			scalar *= 1.1; 
			dy *= 1.1;
			dx *= 1.1;
		}
	}
	/* zoom out */
	function minus() {
		if (scalar > 0.00000000001) {
			mode = 1;
			scalar /= 1.1;
			dy /= 1.1;
			dx /= 1.1;
		}
	}

	/* keep the framerate high and calculate it for correct displaying */
	function nextFrame() {
		fps = 1000.0 / (new Date().getTime() - fps_timestamp);
		fps_timestamp = new Date().getTime();
		if (Presentation.debug == true) {
			window.setTimeout(move, 0);
		} else {
			window.requestNextAnimationFrame(move);
		}
	}

	/* the heartbeat */
	function move() {
		updateLocation(1.0 / fps);
		Objects.foreach(updateObject);
		RenderEngine.render(step, fps);
		/* break the infinite loop */
		if (running == true)
			nextFrame();
	}

	/* apply all effects to non bufferable objects */
	function updateObject(obj) {
		obj.applyEffects(getStepPercent(), step);
		if (obj.bufferable == false) {
			RenderEngine.appendLiveRenderQueue(obj);
		}
	}

	/* calculate new camera position */
	/* time param is in seconds */
	function updateLocation(time) {
		frame += time;
		updateTimer(time);
		var partial = RenderEngine.getPartial();
		if (mode == 0) {
			dx = getDX(getZoom() / partial);
			dy = getDY(getZoom() / partial);
			scalar = getZoom();
			angle = getRotation();
		}
		RenderEngine.setCamera({
			x : dx,
			y : dy,
			zoom : scalar,
			angle : angle
		});
	}
	
	/* update the timer */
	function updateTimer(time) {
		frame += 100.0*direction*time/next.stop;
		if (frame >= 100) {
			frame = 100;
		} else if (frame <= 0) {
			frame = 0;
		}
	}

	/* helper function to interpolate from a to b */
	function interpolate(x, b, c) {
		return b + (x / 100 * (c - b));
	}
	/* prints out the percentage of the current step */
	function getStepPercent() {
		return frame;
	}
	/* calculate dx */
	function getDX(partial) {
		return interpolate(getStepPercent(), -last.x, -next.x) * partial;
	}
	/* calculate dy */
	function getDY(partial) {
		return interpolate(getStepPercent(), -last.y, -next.y) * partial;
	}
	/* calculate the current zoom lvl */
	function getZoom() {
		/* Mathematisches-Modell: Zinsrechnung a*((b/a)^x) = b x ist die Interpolation als Wert zwischen 0 und 1 */
		return Math.pow (next.zoom/last.zoom, getStepPercent()/100)*last.zoom;
	}

	/* calculate current rotation */
	function getRotation() {
		return interpolate(getStepPercent(), last.angle, next.angle);
	}

	
	this.addObject = function (obj) {
		Objects.append(obj);
	};
	this.addPathpoint = function (pathpoint) {
		Path.append(pathpoint);
	};
	this.removeObject = function (obj) {
		Objects.remove(obj);
	};
	this.removePathpoint = function (pathpoint) {
		Path.remove(pathpoint);
	};
	
	this.getStepsCount = function () {
		return Path.getLength();
	};
	
	this.launch = function(Param_RenderEngine) {
		RenderEngine = Param_RenderEngine;
		running = true;
		nextFrame();
	};
	this.init = function(canvas) {
		attachEvent(canvas, 'mousedown', onmousedown);
		attachEvent(canvas, 'mouseup', onmouseup);
		attachEvent(canvas, 'mousemove', onmousemove);
		attachEvent(window, 'DOMMouseScroll', onwheel);
		attachEvent(window, 'mousewheel', onwheel);
		attachEvent(document, 'mousewheel', onwheel);
		attachEvent(window, 'scroll', onwheel);
		attachEvent(document, 'scroll', onwheel);
		window.onkeydown = onkeydown;
	};
	this.stop = function() {
		running = false;
	};
}
